import WatchNumber from "../WatchNumber"
import WatchValue from "../WatchValue";

export interface WatchRect {
    readonly Left: WatchNumber;
    readonly Top: WatchNumber;
    readonly Width: WatchNumber;
    readonly Height: WatchNumber;
}

export interface WatchAround {
    readonly Top: WatchNumber;
    readonly Right: WatchNumber;
    readonly Bottom: WatchNumber;
    readonly Left: WatchNumber;
}

export interface BlockStyle<T> {
    Direction: "row" | "column" | "row-reverse" | "column-reverse";
    Width: undefined | number | T;
    Height: undefined | number | T;
    Padding: undefined | number;
    PaddingTop: undefined | number;
    PaddingRight: undefined | number;
    PaddingBottom: undefined | number;
    PaddingLeft: undefined | number;
    Left: undefined | number;
    Right: undefined | number;
    Top: undefined | number;
    Bottom: undefined | number;
    Position: undefined | "relative" | "absolute";
    Flex: undefined | number;
    Align: "left" | "center" | "right";
}

export type CalcSizeType = "child" | "auto" | "fixed" | "absolute" | "flex";

export interface CalcValueType {
    UsableWidth: number;
    UsableHeight: number;
    Padding: WatchAround;
    Position: WatchAround;
    InAbsolute: WatchValue<boolean>;
    WidthType: WatchValue<CalcSizeType>;
    HeightType: WatchValue<CalcSizeType>;
    WidthFlex: WatchValue<number>;
    HeightFlex: WatchValue<number>;
}

type BlockStyleA = BlockStyle<string>;
type BlockStyleB = BlockStyle<`${number}%`>;

export type WatchBlockStyle = { readonly [name in keyof BlockStyleA]: WatchValue<BlockStyleA[name]> };

export type BlockStyleParams = {
    [name in keyof BlockStyleB]?: BlockStyleB[name] | WatchValue<BlockStyleB[name]>
} & { Childrens?: WatchBlock[] } & {
    StyleSheet?: {
        [name: string]: {
            [name in keyof BlockStyleB]?: BlockStyleB[name] | WatchValue<BlockStyleB[name]>
        }
    }
};

export const MakeWatchRect = function (): WatchRect {
    return {
        Left: new WatchNumber(0),
        Top: new WatchNumber(0),
        Width: new WatchNumber(0),
        Height: new WatchNumber(0),
    }
};

export const MakeWatchAround = function (): WatchAround {
    return {
        Top: new WatchNumber(0),
        Right: new WatchNumber(0),
        Bottom: new WatchNumber(0),
        Left: new WatchNumber(0),
    }
};

/** 监视盒子类 */
export default class WatchBlock {

    public Name: string = "";

    public readonly Father = new WatchValue<undefined | WatchBlock>(undefined);

    public readonly Space = MakeWatchRect();

    public readonly Client = MakeWatchRect();

    public readonly Content = MakeWatchRect();

    private on_calc: boolean = false;
    private has_double_calc: boolean = false;

    public readonly Style: WatchBlockStyle = {
        Direction: new WatchValue("column"),
        Width: new WatchValue(undefined),
        Height: new WatchValue(undefined),
        Padding: new WatchValue(undefined),
        PaddingTop: new WatchValue(undefined),
        PaddingRight: new WatchValue(undefined),
        PaddingBottom: new WatchValue(undefined),
        PaddingLeft: new WatchValue(undefined),
        Left: new WatchValue(undefined),
        Right: new WatchValue(undefined),
        Top: new WatchValue(undefined),
        Bottom: new WatchValue(undefined),
        Position: new WatchValue(undefined),
        Flex: new WatchValue(undefined),
        Align: new WatchValue("left"),
    };

    private readonly CalcV: CalcValueType = {
        UsableWidth: 0,
        UsableHeight: 0,
        Padding: MakeWatchAround(),
        Position: MakeWatchAround(),
        InAbsolute: new WatchValue<boolean>(false),
        WidthType: new WatchValue<CalcSizeType>("child"),
        HeightType: new WatchValue<CalcSizeType>("child"),
        WidthFlex: new WatchValue<number>(0),
        HeightFlex: new WatchValue<number>(0),
    };

    private readonly _Childrens = new WatchValue<WatchBlock[]>([]);

    public get Childrens() {
        return [...this._Childrens.Value];
    }

    public Append(...child: WatchBlock[]) {
        child.forEach(item => item.Father.Value = this);
        this._Childrens.Value = [...this._Childrens.Value, ...child];
    }

    public Remove(child: WatchBlock) {
        child.Father.Value = undefined;
        this._Childrens.Value = this._Childrens.Value.filter(item => item !== child);
    }

    public constructor(name: string, params?: BlockStyleParams) {
        this.Name = name;
        if (params !== undefined) {
            for (const _name in params) {
                const name = _name as keyof BlockStyleParams;
                if (name === "StyleSheet") { }
                else if (name === "Childrens") {
                    const children = params[name];
                    if (children !== undefined) {
                        children.forEach(child => child.Father.Value = this);
                        this._Childrens.Value = children;
                    }
                } else {
                    const param = params[name];
                    if (param instanceof WatchValue) {
                        this.Style[name].Bind(param as any);
                    } else {
                        this.Style[name].Value = param;
                    }
                }
            }
            if (params["StyleSheet"] !== undefined) {
                const StyleSheet = params["StyleSheet"];
                if (StyleSheet !== undefined) {
                    for (const queryName in StyleSheet) {
                        const element = this.QuerySelector(queryName);
                        const styleSheet = StyleSheet[queryName];
                        for (const attr in styleSheet) {
                            const attrName = attr as keyof BlockStyleA;
                            const value = styleSheet[attrName];
                            if (value instanceof WatchValue) {
                                element.Style[attrName].Bind(value as any);
                            } else {
                                element.Style[attrName].Value = value;
                            }
                        }
                    }
                }
            }
        }

        const { Father, Style, CalcV: { Padding, Position, InAbsolute, WidthFlex, HeightFlex, WidthType, HeightType }, _Childrens } = this;

        Padding.Top.BindCall(() => Style.PaddingTop.Value || Style.Padding.Value || 0, Style.PaddingTop, Style.Padding);
        Padding.Right.BindCall(() => Style.PaddingRight.Value || Style.Padding.Value || 0, Style.PaddingRight, Style.Padding);
        Padding.Bottom.BindCall(() => Style.PaddingBottom.Value || Style.Padding.Value || 0, Style.PaddingBottom, Style.Padding);
        Padding.Left.BindCall(() => Style.PaddingLeft.Value || Style.Padding.Value || 0, Style.PaddingLeft, Style.Padding);

        Position.Top.BindCall(() => Style.Position.Value === undefined ? 0 : (Style.Top.Value || 0), Style.Position, Style.Top);
        Position.Right.BindCall(() => Style.Position.Value === undefined ? 0 : (Style.Right.Value || 0), Style.Position, Style.Right);
        Position.Bottom.BindCall(() => Style.Position.Value === undefined ? 0 : (Style.Bottom.Value || 0), Style.Position, Style.Bottom);
        Position.Left.BindCall(() => Style.Position.Value === undefined ? 0 : (Style.Left.Value || 0), Style.Position, Style.Left);

        InAbsolute.BindCall(() => Father.Value !== undefined && Style.Position.Value === "absolute", Father, Style.Position);

        WidthType.BindCall(() =>
            Style.Width.Value !== undefined
                ? (typeof Style.Width.Value === "string" ? "auto" : "fixed")
                : (InAbsolute.Value && Style.Left.Value !== undefined && Style.Right.Value !== undefined) ? "absolute"
                    : Style.Flex.Value !== undefined ? "flex"
                        : "child"
            , Style.Width, Father, InAbsolute, Style.Left, Style.Right, Style.Flex);
        HeightType.BindCall(() =>
            Style.Height.Value !== undefined
                ? (typeof Style.Height.Value === "string" ? "auto" : "fixed")
                : (InAbsolute.Value && Style.Top.Value !== undefined && Style.Bottom.Value !== undefined) ? "absolute"
                    : Style.Flex.Value !== undefined ? "flex"
                        : "child"
            , Style.Height, Father, InAbsolute, Style.Top, Style.Bottom, Style.Flex);

        WidthFlex.BindCall(() => WidthType.Value === "flex" ? Style.Flex.Value || 0 : 0, WidthType, Style.Flex);
        HeightFlex.BindCall(() => HeightType.Value === "flex" ? Style.Flex.Value || 0 : 0, HeightType, Style.Flex);

        this.BindChanger();
    }

    private BindChanger(): void {
        const { Style, _Childrens } = this;

        for (const _name in Style) {
            const name = _name as keyof WatchBlockStyle;
            Style[name].Add(null, () => { this.CalcUi(); });
        }
        _Childrens.Add(null, () => { this.CalcUi(); });

        // this.CalcUi();
    }

    /**
     * 从父元素开始计算界面布局
     */
    public CalcUi(): void {
        if (this.Father.Value !== undefined) {
            let temp: WatchBlock = this;
            let father: WatchBlock | undefined = this.Father.Value;
            while ((father = temp.Father.Value) !== undefined) {
                temp = father;
            }
            temp.CalcUi();
            return;
        }
        if (this.on_calc) {
            this.has_double_calc = true;
            return;
        }
        this.on_calc = true;
        this.CalcFixedSize();
        this.CalcChildFixedSize();
        this.CalcAbsoluteSize();
        this.on_calc = false;
        if (this.has_double_calc) {
            this.has_double_calc = false;
            this.CalcUi();
        }
    }

    /**
     * 计算所有元素的固定大小
     */
    private CalcFixedSize(): void {
        const { Space, Client, Content, Style, CalcV, CalcV: { Padding, WidthType, HeightType }, _Childrens } = this;

        CalcV.UsableWidth = 0;
        CalcV.UsableHeight = 0;

        this.CalcPositionBySpace();

        if (WidthType.Value === "fixed") {
            Content.Width.Value = <number>Style.Width.Value;
            Space.Width.Value = Content.Width.Value + Padding.Left.Value + Padding.Right.Value;
            Client.Width.Value = Space.Width.Value;
        }
        if (HeightType.Value === "fixed") {
            Content.Height.Value = <number>Style.Height.Value;
            Space.Height.Value = Content.Height.Value + Padding.Top.Value + Padding.Bottom.Value;
            Client.Height.Value = Space.Height.Value;
        }

        _Childrens.Value.forEach(child => { child.CalcFixedSize(); });
    }

    /**
     * 按占用坐标计算绘制坐标
     */
    private CalcPositionBySpace(): void {
        const { Space, Client, Content, Style, CalcV: { Padding } } = this;

        Client.Top.Value = Space.Top.Value;
        Client.Left.Value = Space.Left.Value;
        const position = Style.Position.Value;

        if (position === "relative") {
            if (Style.Left.Value !== undefined) {
                Client.Left.Value += Style.Left.Value;
            } else if (Style.Right.Value !== undefined) {
                Client.Left.Value -= Style.Right.Value;
            }
            if (Style.Top.Value !== undefined) {
                Client.Top.Value += Style.Top.Value;
            } else if (Style.Bottom.Value !== undefined) {
                Client.Top.Value -= Style.Bottom.Value;
            }
        } else if (position === "absolute") {
            if (Style.Left.Value !== undefined) {
                Client.Left.Value += Style.Left.Value;
            }
            if (Style.Top.Value !== undefined) {
                Client.Top.Value += Style.Top.Value;
            }
        }

        Content.Top.Value = Client.Top.Value + Padding.Top.Value;
        Content.Left.Value = Client.Left.Value + Padding.Left.Value;
    }

    /**
     * 计算子元素固定大小
     */
    private CalcChildFixedSize(): void {
        const { Style, CalcV, _Childrens } = this;

        const direction = Style.Direction.Value;
        const isRow = direction === "row" || direction === "row-reverse";

        _Childrens.Value.forEach(child => {
            child.CalcChildFixedSize();
            if (!child.CalcV.InAbsolute.Value) {
                if (isRow) {
                    const widthType = child.CalcV.WidthType.Value;
                    if (widthType === "fixed" || widthType === "child" || widthType === "auto") {
                        if (CalcV.UsableWidth < child.Space.Width.Value) {
                            CalcV.UsableWidth = child.Space.Width.Value;
                        }
                        CalcV.UsableHeight += child.Space.Height.Value;
                    }
                } else {
                    const heightType = child.CalcV.HeightType.Value;
                    if (heightType === "fixed" || heightType === "child" || heightType === "auto") {
                        CalcV.UsableWidth += child.Space.Width.Value;
                        if (CalcV.UsableHeight < child.Space.Height.Value) {
                            CalcV.UsableHeight = child.Space.Height.Value;
                        }
                    }
                }
            }
        });

        this.CalcSizeByChild();
    }

    /**
     * 计算元素大小
     */
    private CalcSizeByChild(): void {
        const { Space, Client, Content, CalcV: { UsableWidth, UsableHeight, Padding, WidthType, HeightType } } = this;

        if (WidthType.Value === "child") {
            Content.Width.Value = UsableWidth;
            Space.Width.Value = Padding.Left.Value + Content.Width.Value + Padding.Right.Value;
            Client.Width.Value = Space.Width.Value;
        }
        if (HeightType.Value === "child") {
            Content.Height.Value = UsableHeight;
            Space.Height.Value = Padding.Top.Value + Content.Height.Value + Padding.Bottom.Value;
            Client.Height.Value = Space.Height.Value;
        }
    }

    /**
     * 计算绝对定位的元素大小
     */
    private CalcAbsoluteSize() {
        const { Father, Space, Client, Content, CalcV: { Padding, Position, WidthType, HeightType }, Style } = this;

        const father = Father.Value;
        if (father !== undefined) {
            if (WidthType.Value === "absolute") {
                Space.Width.Value = father.Client.Width.Value - Position.Left.Value - Position.Right.Value;
                Client.Width.Value = Space.Width.Value;
                Content.Width.Value = Client.Width.Value - Padding.Left.Value - Padding.Right.Value;
            } else if (WidthType.Value === "auto") {
                Content.Width.Value = father.Content.Width.Value * (parseFloat(<string>Style.Width.Value) / 100);
                Space.Width.Value = Content.Width.Value + Padding.Left.Value + Padding.Right.Value;
                Client.Width.Value = Space.Width.Value;
            }
            if (HeightType.Value === "absolute") {
                Space.Height.Value = father.Client.Height.Value - Position.Top.Value - Position.Bottom.Value;
                Client.Height.Value = Space.Height.Value;
                Content.Height.Value = Client.Height.Value - Padding.Top.Value - Padding.Bottom.Value;
            } else if (HeightType.Value === "auto") {
                Content.Height.Value = father.Content.Height.Value * (parseFloat(<string>Style.Height.Value) / 100);
                Space.Height.Value = Content.Height.Value + Padding.Top.Value + Padding.Bottom.Value;
                Client.Height.Value = Space.Height.Value;
            }
        }

        this.CalcChildPosition();
    }

    /**
     * 计算子元素位置
     */
    private CalcChildPosition(): void {
        const { Client, Content, Style, CalcV, _Childrens } = this;

        const direction = Style.Direction.Value;
        const align = Style.Align.Value;
        const isRow = direction === "row" || direction === "row-reverse";
        const childrens = (direction === "row-reverse" || direction === "column-reverse" ? [..._Childrens.Value].reverse() : _Childrens.Value);

        const top = Content.Top.Value;
        const left = Content.Left.Value;

        const flexCount = childrens.reduce((count, { CalcV: { WidthFlex, HeightFlex } }) => {
            return count + (isRow ? WidthFlex : HeightFlex).Value
        }, 0) || 1;

        const hanldeInfinity = (num: number) => isFinite(num) ? num : 0;

        const flexWidth = hanldeInfinity((Content.Width.Value - CalcV.UsableWidth) / flexCount);
        const flexHeight = hanldeInfinity((Content.Height.Value - CalcV.UsableHeight) / flexCount);

        let calcWidth = 0;
        let calcHeight = 0;

        childrens.forEach(child => {
            const isAbsolute = child.CalcV.InAbsolute.Value;

            const widthType = child.CalcV.WidthType.Value;
            if (isAbsolute) {
                // 非文档流的左边距，在css以父元素border为标准，但是目前设计margin，还是以padding内为准
                if (child.Style.Left.Value !== undefined) {
                    child.Space.Left.Value = left + child.Style.Left.Value;
                } else if (child.Style.Right.Value !== undefined) {
                    child.Space.Left.Value = left + Content.Width.Value - child.Content.Width.Value - child.Style.Right.Value;
                } else {
                    child.Space.Left.Value = left;
                }
            } else {
                if (widthType === "flex") {
                    if (isRow) {
                        child.Space.Width.Value = Content.Width.Value;
                    } else {
                        child.Space.Width.Value = flexWidth * child.CalcV.WidthFlex.Value;
                    }
                    child.Client.Width.Value = child.Space.Width.Value;
                    child.Content.Width.Value = child.Client.Width.Value - child.CalcV.Padding.Left.Value - child.CalcV.Padding.Right.Value;
                }
                if (isRow) {
                    if (align === "left") {
                        child.Space.Left.Value = left;
                    } else if (align === "center") {
                        child.Space.Left.Value = left + (Content.Width.Value - child.Space.Width.Value) / 2;
                    } else if (align === "right") {
                        child.Space.Left.Value = left + Content.Width.Value - child.Space.Width.Value;
                    }
                } else {
                    child.Space.Left.Value = left + calcWidth;
                }
            }

            const heightType = child.CalcV.HeightType.Value;
            if (isAbsolute) {
                if (child.Style.Top.Value !== undefined) {
                    child.Space.Top.Value = top + child.Style.Top.Value;
                } else if (child.Style.Bottom.Value !== undefined) {
                    child.Space.Top.Value = top + Content.Height.Value - child.Content.Height.Value - child.Style.Bottom.Value;
                } else {
                    child.Space.Top.Value = top;
                }
            } else {
                if (heightType === "flex") {
                    if (isRow) {
                        child.Space.Height.Value = flexHeight * child.CalcV.HeightFlex.Value;
                    } else {
                        child.Space.Height.Value = Content.Height.Value;
                    }
                    child.Client.Height.Value = child.Space.Height.Value;
                    child.Content.Height.Value = child.Client.Height.Value - child.CalcV.Padding.Top.Value - child.CalcV.Padding.Bottom.Value;
                }
                if (isRow) {
                    child.Space.Top.Value = top + calcHeight;
                } else {
                    if (align === "left") {
                        child.Space.Top.Value = top;
                    } else if (align === "center") {
                        child.Space.Top.Value = top + (Content.Height.Value - child.Space.Height.Value) / 2;
                    } else if (align === "right") {
                        child.Space.Top.Value = top + Content.Height.Value - child.Space.Height.Value;
                    }
                }
            }

            if (!child.CalcV.InAbsolute.Value) {
                if (isRow) {
                    if (widthType !== "absolute") {
                        if (calcWidth < child.Space.Width.Value) {
                            calcWidth = child.Space.Width.Value;
                        }
                        calcHeight += child.Space.Height.Value;
                    }
                } else {
                    if (heightType !== "absolute") {
                        calcWidth += child.Space.Width.Value;
                        if (calcHeight < child.Space.Height.Value) {
                            calcHeight = child.Space.Height.Value;
                        }
                    }
                }
            }
            child.CalcPositionBySpace();
        });

        _Childrens.Value.forEach(child => { child.CalcAbsoluteSize(); });
    }

    public QuerySelector(name: string): WatchBlock {
        const findInfo = this.querySelector(name, [this]);
        if (findInfo === undefined) {
            throw new Error("未知名称");
        }
        return findInfo;
    }

    public QuerySelectorU(name: string): WatchBlock | undefined {
        return this.querySelector(name, [this]);
    }

    private querySelector(name: string, list: WatchBlock[]): WatchBlock | undefined {
        for (const info of list) {
            if (info.Name === name) {
                return info;
            }
        }
        for (const info of list) {
            const findChild = this.querySelector(name, info.Childrens);
            if (findChild !== undefined) {
                return findChild;
            }
        }
        return undefined;
    }
}
